﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace FlyingCell
{
    public class UserControl3D
    {
        /// <summary>
        /// The 3D geometry of the UserControl3D
        /// </summary>
        /// <remarks>
        /// The 3D geometry is considered having its center at 0, 0, 0.
        /// </remarks>
        public MeshGeometry3D mGeometry
        {
            get
            {
                return _mGeometry;
            }
            set
            {
                _mGeometry = value;
                (mModel3D.Content as GeometryModel3D).Geometry = _mGeometry;
            }
        }
        private MeshGeometry3D _mGeometry = null;

        /// <summary>
        /// The 2D UserControl of the UserControl3D
        /// </summary>
        public UserControl mUserControl = null;

        /// <summary>
        /// The UserControl3D name
        /// </summary>
        public virtual String mName
        {
            get
            {
                return _mName;
            }

            set
            {
                _mName = value;
            }
        }
        private string _mName = "";

        /// <summary>
        /// Coordinates of the UserControl3D = the position of the center of the geometry.
        /// </summary>
        /// <remarks> 
        /// The center is 0, 0, 0 in the MeshGeometry reference.
        /// </remarks>
        public virtual Point3D mPosition
        {
            get
            {
                return _mPosition;
            }

            set
            {
                _mPosition = value;

                // Unlink animation from the property
                mTranslation.BeginAnimation(TranslateTransform3D.OffsetXProperty, null);
                mTranslation.BeginAnimation(TranslateTransform3D.OffsetYProperty, null);
                mTranslation.BeginAnimation(TranslateTransform3D.OffsetZProperty, null);

                mTranslation.OffsetX = _mPosition.X;
                mTranslation.OffsetY = _mPosition.Y;
                mTranslation.OffsetZ = _mPosition.Z;

                ApplyBoundingBox(null);
            }
        }
        private Point3D _mPosition = new Point3D(0, 0, 0);

        /// <summary>
        /// Rotation coordinates of the UserControl3D
        /// </summary>
        public virtual Point3D mAngles
        {
            get
            {
                return _mAngles;
            }

            set
            {
                _mAngles = value;

                // Unlink animation from the property
                mXRotation.BeginAnimation(RotateTransform3D.RotationProperty, null);
                mYRotation.BeginAnimation(RotateTransform3D.RotationProperty, null);
                mZRotation.BeginAnimation(RotateTransform3D.RotationProperty, null);

                (mXRotation.Rotation as AxisAngleRotation3D).Angle = _mAngles.X;
                (mYRotation.Rotation as AxisAngleRotation3D).Angle = _mAngles.Y;
                (mZRotation.Rotation as AxisAngleRotation3D).Angle = _mAngles.Z;

                ApplyBoundingBox(null);
            }
        }
        private Point3D _mAngles = new Point3D(0, 0, 0);

        /// <summary>
        /// Scale of the UserControl3D
        /// </summary>
        public virtual Point3D mScale
        {
            get
            {
                return _mScale;
            }

            set
            {
                _mScale = value;

                // Unlink animation from the property
                mScaling.BeginAnimation(ScaleTransform3D.ScaleXProperty, null);
                mScaling.BeginAnimation(ScaleTransform3D.ScaleYProperty, null);
                mScaling.BeginAnimation(ScaleTransform3D.ScaleZProperty, null);

                mScaling.ScaleX = _mScale.X;
                mScaling.ScaleY = _mScale.Y;
                mScaling.ScaleZ = _mScale.Z;
            }
        }
        private Point3D _mScale = new Point3D(1, 1, 1);

        /// <summary>
        /// Opacity of the UserControl3D
        /// </summary>
        public virtual double mOpacity
        {
            get
            {
                return _mOpacity;
            }

            set
            {
                _mOpacity = value;
                ((mModel3D.Content as GeometryModel3D).Material as DiffuseMaterial).Brush.Opacity = _mOpacity;
            }
        }
        private double _mOpacity = 1;

        
        /// <summary>
        /// Gives a little offset to the 3D object to be visible compared to an object on the same plan.
        /// </summary>
        public virtual Vector3D mZIndex
        {
            get
            {
                return _mZIndex;
            }
            set
            {
                _mZIndex = value;

                mZIndexTranslation.OffsetX = _mZIndex.X;
                mZIndexTranslation.OffsetY = _mZIndex.Y;
                mZIndexTranslation.OffsetZ = _mZIndex.Z;
            }

        }
        private Vector3D _mZIndex = new Vector3D(0, 0, 0);

        /// <summary>
        /// Contains the ModelVisual3D to be added to the scene. 
        /// </summary>
        private ModelVisual3D mModel3D = null;

        /// <summary>
        /// The UserControl3D translation
        /// </summary>
        private TranslateTransform3D mTranslation = null;

        /// <summary>
        /// The UserControl3D translation
        /// </summary>
        //private TranslateTransform3D mSelfTranslation = null;

        /// <summary>
        /// The UserControl3D translation
        /// </summary>
        private TranslateTransform3D mZIndexTranslation = null;

        ///// <summary>
        ///// The UserControl3D X rotation
        ///// </summary>
        private RotateTransform3D mXRotation = null;

        ///// <summary>
        ///// The UserControl3D Y rotation
        ///// </summary>
        private RotateTransform3D mYRotation = null;

        ///// <summary>
        ///// The UserControl3D Z rotation
        ///// </summary>
        private RotateTransform3D mZRotation = null;

        /// <summary>
        /// The UserControl3D scaling
        /// </summary>
        private ScaleTransform3D mScaling = null;

        /// <summary>
        /// Specifiy the bounding box in which the UserControl3D is going to navigate
        /// </summary>
        private BoundingBox mBoundingBox = null;

        /// <summary>
        /// Remember the Viewport3D
        /// </summary>
        private Viewport3D mViewport = null;

        /// <summary>
        /// Protect against multiple addition to the Viewport3D
        /// </summary>
        private bool IsAddedTo3DScene = false;

        /// <summary>
        /// UserControl3D selection state
        /// </summary>
        private bool IsUCSelected = false;

        /// <summary>
        /// The UserControl3D material is external
        /// </summary>
        private bool IsVisualExternal = false;

        /// <summary>
        /// Report the UserControl3D status and properties.
        /// </summary>
        public class UserControl3DStatusReport : EventArgs
        {
            public UserControl3DStatusReport(UserControl3D UserControl3D)
            {
                UserControl3DPosition = UserControl3D.mPosition;
                UserControl3DAngles = UserControl3D.mAngles;
                UserControl3DScale = UserControl3D.mScale;
                UserControl3DOpacity = UserControl3D.mOpacity;
            }

            public Point3D UserControl3DPosition;
            public Point3D UserControl3DAngles;
            public Point3D UserControl3DScale;
            public double UserControl3DOpacity;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public UserControl3D()
        {
            Initialize(null, null, null);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public UserControl3D(string Name)
        {
            Initialize(Name, null, null);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public UserControl3D(string Name, MeshGeometry3D Geometry)
        {
            Initialize(Name, Geometry, null);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public UserControl3D(string Name, MeshGeometry3D Geometry, UserControl UserControl)
        {
            Initialize(Name, Geometry, UserControl);
        }

        /// <summary>
        /// Initializer
        /// </summary>
        public void Initialize(string Name, MeshGeometry3D Geometry, UserControl UserControl)
        {
            // Global init
            _mName = Name;

            // 3D model init
            GeometryModel3D GeometryModel = new GeometryModel3D();

            if (Geometry != null)
            {
                GeometryModel.Geometry = Geometry;
            }

            // RotateTransform3D( Axis="1,0,0", Angle="0", CenterX="0", CenterY="0", CenterZ="0" )
            //mSelfTranslation = new TranslateTransform3D(0, 0, 0);
            mScaling = new ScaleTransform3D(1, 1, 1, 0, 0, 0);
            mXRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), 0), 0, 0, 0);
            mYRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), 0), 0, 0, 0);
            mZRotation = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 0), 0, 0, 0);
            mZIndexTranslation = new TranslateTransform3D(_mZIndex);
            mTranslation = new TranslateTransform3D(0, 0, 0);

            // Order of addition is extra important
            Transform3DGroup TransGroup = new Transform3DGroup();
            //TransGroup.Children.Add(mSelfTranslation);
            TransGroup.Children.Add(mScaling);
            TransGroup.Children.Add(mXRotation);
            TransGroup.Children.Add(mYRotation);
            TransGroup.Children.Add(mZRotation);
            TransGroup.Children.Add(mZIndexTranslation);
            TransGroup.Children.Add(mTranslation);
            GeometryModel.Transform = TransGroup;

            mModel3D = new ModelVisual3D();
            mModel3D.Content = GeometryModel;

            // UserControl init
            if (UserControl != null)
            {
                mUserControl = UserControl;
                SetVisualContent(null);
            }
            else
            {
                SetVisualContent(new SolidColorBrush(Colors.Blue));
            }


        }

        /// <summary>
        /// Update the content of the UserControl3D
        /// </summary>
        public virtual void StartUpdateContent(DispatcherObject Disp)
        {
            Disp.Dispatcher.Invoke((ThreadStart)delegate() { UpdateContent_Completed(); }, null);
        }
        /// <summary>
        /// Update complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> UpdateContentCompleted;

        /// <summary>
        /// Raise Update completed event
        /// </summary>
        private void UpdateContent_Completed()
        {
            if (!IsVisualExternal)
            {
                SetVisualContent(null);
            }

            if (UpdateContentCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                UpdateContentCompleted(this, Args);
            }
        }

        /// <summary>
        /// Set/Unset the back material with the same content as the front material
        /// </summary>
        /// <param name="Set">Set if true else Unset</param>
        public virtual void SetBackContent(bool Set)
        {
            if (mModel3D.Content != null)
            {
                if (Set)
                {
                    (mModel3D.Content as GeometryModel3D).BackMaterial = (mModel3D.Content as GeometryModel3D).Material;
                }
                else
                {
                    (mModel3D.Content as GeometryModel3D).BackMaterial = null;
                }
            }
        }

        /// <summary>
        /// Set the UserControl3D material.
        /// Give the possibility to set the material to something else than the internal UserControl
        /// </summary>
        /// <param name="Brush">An external brush (If null, set the material to the internal UseControl)</param>
        public virtual void SetVisualContent(Brush Brush)
        {
            if (Brush != null)
            {
                if (mModel3D.Content != null)
                {
                    (mModel3D.Content as GeometryModel3D).Material = new DiffuseMaterial(Brush);
                    IsVisualExternal = true;
                }
            }
            else
            {
                // Must be set before
                IsVisualExternal = false;

                if (IsUCSelected)
                {
                    MakeDynamic();
                }
                else
                {
                    MakeStatic();
                }
            }
        }

        /// <summary>
        /// Make the UserControl3D material brush a visual brush (Thus dynamic, but less performant in rendering)
        /// </summary>
        /// <remarks>Works only if visual content is the internal UserControl</remarks>
        private void MakeDynamic()
        {
            if ((mModel3D.Content != null) && (!IsVisualExternal))
            {
                // BE CAREFULL! If the usercontrol is not completly filled the visual will be stretched
                VisualBrush TmpVisualBrush = new VisualBrush(mUserControl);
                (mModel3D.Content as GeometryModel3D).Material = new DiffuseMaterial(TmpVisualBrush);
            }
        }

        /// <summary>
        /// Make the UserControl3D material brush a bitmap image (Thus static and more rendering performant, but not interactive anymore)
        /// </summary>
        /// <remarks>Works only if visual content is the internal UserControl</remarks>
        private void MakeStatic()
        {
            if ((mModel3D.Content != null) && (!IsVisualExternal))
            {
                RenderTargetBitmap TmpBitmap = new RenderTargetBitmap((int)mUserControl.Width, (int)mUserControl.Height, 96, 96, PixelFormats.Default);
                mUserControl.Arrange(new Rect(0, 0, mUserControl.Width, mUserControl.Height));
                mUserControl.UpdateLayout();
                TmpBitmap.Clear();
                TmpBitmap.Render(mUserControl);

                ImageBrush TmpImageBrush = new ImageBrush(TmpBitmap);

                // Trial for picture display
                //ImageBrush TmpImageBrush = new ImageBrush();
                //TmpImageBrush.ImageSource = new BitmapImage(new Uri(@"static.jpg", UriKind.Relative));
                //TmpImageBrush.Stretch = Stretch.Fill;

                (mModel3D.Content as GeometryModel3D).Material = new DiffuseMaterial(TmpImageBrush);
            }
        }

        /// <summary>
        /// Apply a bounding box to the UserControl3D and remember it.
        /// </summary>
        /// <param name="Box">The bounding box (can be null to use the registered one)</param>
        public virtual void ApplyBoundingBox(BoundingBox Box)
        {
            if (Box != null)
            {
                mBoundingBox = Box;
            }

            if (mBoundingBox != null)
            {
                if (mBoundingBox.IsPointInBoundingBox(_mPosition))
                {
                    AddTo3DScene(null);
                }
                else
                {
                    RemoveFrom3DScene();
                }
            }
        }

        /// <summary>
        /// Cancel the bounding box around the UserControl3D, but keep it in memory.
        /// </summary>
        public virtual void CancelBoundingBox()
        {
            AddTo3DScene(null);
        }

        /// <summary>
        /// Add the UserControl3D into the 3D scene and apply the bounding box
        /// </summary>
        /// <param name="Viewport">the 3D scene (can be null to use the registered one)</param>
        /// <param name="Box">The bounding box in which Cell is considered (can be null to use the registered one)</param>
        public virtual void AddTo3DSceneWithBounding(Viewport3D Viewport, BoundingBox Box)
        {
            if (Viewport != null)
            {
                mViewport = Viewport;
            }

            if (Box != null)
            {
                mBoundingBox = Box;
            }

            ApplyBoundingBox(null);
        }

        /// <summary>
        /// Add the UserControl3D into the 3D scene
        /// </summary>
        /// <param name="Viewport">The 3D scene (can be null to use the registered one)</param>
        public virtual void AddTo3DScene(Viewport3D Viewport)
        {
            if (Viewport != null)
            {
                mViewport = Viewport;
            }

            if (mViewport != null)
            {
                if (!IsAddedTo3DScene)
                {
                    mViewport.Children.Add(mModel3D);
                    IsAddedTo3DScene = true;
                }
            }
        }

        /// <summary>
        /// Remove the UserControl3D from the 3D scene
        /// </summary>
        public virtual void RemoveFrom3DScene()
        {
            if (mViewport != null)
            {
                if (IsAddedTo3DScene)
                {
                    mViewport.Children.Remove(mModel3D);
                    IsAddedTo3DScene = false;
                }
            }
        }

        /// <summary>
        /// Select the UserControl3D.
        /// </summary>
        /// <remarks>
        /// Virtual => derived classes must call this method at the end of the override.
        /// (ex: base.Select();)
        /// Selecting the UserControl3D makes it dynamic. 
        /// </remarks>
        public virtual void Select()
        {
            if (!IsUCSelected)
            {
                MakeDynamic();
                IsUCSelected = true;
            }
        }

        /// <summary>
        /// Unselect the UserControl3D.
        /// </summary>
        /// <remarks>
        /// Virtual => derived classes must call this method at the end of the override.
        /// (ex: base.Unselect();)
        /// Unselecting the UserControl3D makes it static. 
        /// </remarks>
        public virtual void Unselect()
        {
            if (IsUCSelected)
            {
                MakeStatic();
                IsUCSelected = false;
            }
        }


        //-------------------------------------------
        // SELF TRANSLATION
        //-------------------------------------------

        ///// <summary>
        ///// Start LeftRight translation animation
        ///// </summary>
        ///// <param name="Offset">Length of displacement</param>
        ///// <param name="DurationMs">Duration in millisecond</param>
        //public virtual void StartLeftRightTranslation(double Offset, int DurationMs)
        //{
        //    // Add the Cell is the 3D scene if it will come into the bounding box
        //    //Point3D NextPosition = new Point3D(_mPosition.X + Offset, _mPosition.Y, _mPosition.Z);
        //    //if ((mBoundingBox != null) && (mBoundingBox.IsPointInBoundingBox(NextPosition)))
        //    //{
        //    //    AddTo3DScene(null);
        //    //}

        //    if ((IsAddedTo3DScene) && (DurationMs != 0))
        //    {
        //        DoubleAnimation TmpAnimation = new DoubleAnimation();

        //        TmpAnimation.From = 0;
        //        TmpAnimation.To = 0 + Offset;
        //        TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
        //        TmpAnimation.Completed += LeftRightTranslation_Completed;

        //        mSelfTranslation.BeginAnimation(TranslateTransform3D.OffsetXProperty, TmpAnimation);
        //    }
        //    //else
        //    //{
        //    //    mPosition = NextPosition;

        //    //    // raise event
        //    //    if (XTranslationCompleted != null)
        //    //    {
        //    //        UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
        //    //        XTranslationCompleted(this, Args);
        //    //    }
        //    //}
        //}

        ///// <summary>
        ///// Translation complete event on which external class can register a handler
        ///// </summary>
        //public virtual event EventHandler<UserControl3DStatusReport> LeftRightTranslationCompleted;

        ///// <summary>
        ///// Translation completed event handler
        ///// </summary>
        ///// <param name="sender">This</param>
        ///// <param name="e">EventArgs</param>
        //private void LeftRightTranslation_Completed(object sender, EventArgs e)
        //{
        //    //_mPosition.X = mTranslation.OffsetX;
        //    //_mPosition.Y = mTranslation.OffsetY;
        //    //_mPosition.Z = mTranslation.OffsetZ;

        //    //ApplyBoundingBox(null);

        //    // raise event
        //    if (LeftRightTranslationCompleted != null)
        //    {
        //        UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
        //        LeftRightTranslationCompleted(this, Args);
        //    }
        //}

        //-------------------------------------------
        // XYZ TRANSLATION
        //-------------------------------------------

        /// <summary>
        /// Start X translation animation
        /// </summary>
        /// <param name="Offset">Length of displacement</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartXTranslation(double Offset, int DurationMs)
        {
            // Add the Cell is the 3D scene if it will come into the bounding box
            Point3D NextPosition = new Point3D(_mPosition.X + Offset, _mPosition.Y, _mPosition.Z);
            if ((mBoundingBox != null) && (mBoundingBox.IsPointInBoundingBox(NextPosition)))
            {
                AddTo3DScene(null);
            }

            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                DoubleAnimation TmpAnimation = new DoubleAnimation();

                TmpAnimation.From = _mPosition.X;
                TmpAnimation.To = _mPosition.X + Offset;
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += XTranslation_Completed;

                mTranslation.BeginAnimation(TranslateTransform3D.OffsetXProperty, TmpAnimation);
            }
            else
            {
                mPosition = NextPosition;

                // raise event
                if (XTranslationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    XTranslationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Translation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> XTranslationCompleted;

        /// <summary>
        /// Translation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void XTranslation_Completed(object sender, EventArgs e)
        {
            _mPosition.X = mTranslation.OffsetX;
            _mPosition.Y = mTranslation.OffsetY;
            _mPosition.Z = mTranslation.OffsetZ;

            ApplyBoundingBox(null);

            // raise event
            if (XTranslationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                XTranslationCompleted(this, Args);
            }
        }

        /// <summary>
        /// Start Y translation animation
        /// </summary>
        /// <param name="Offset">Length of displacement</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartYTranslation(double Offset, int DurationMs)
        {
            // Add the Cell is the 3D scene if it will come into the bounding box
            Point3D NextPosition = new Point3D(_mPosition.X, _mPosition.Y + Offset, _mPosition.Z);
            if ((mBoundingBox != null) && (mBoundingBox.IsPointInBoundingBox(NextPosition)))
            {
                AddTo3DScene(null);
            }

            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                DoubleAnimation TmpAnimation = new DoubleAnimation();

                TmpAnimation.From = _mPosition.Y;
                TmpAnimation.To = _mPosition.Y + Offset;
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += YTranslation_Completed;

                mTranslation.BeginAnimation(TranslateTransform3D.OffsetYProperty, TmpAnimation);
            }
            else
            {
                mPosition = NextPosition;

                // raise event
                if (YTranslationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    YTranslationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Translation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> YTranslationCompleted;

        /// <summary>
        /// Translation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void YTranslation_Completed(object sender, EventArgs e)
        {
            _mPosition.X = mTranslation.OffsetX;
            _mPosition.Y = mTranslation.OffsetY;
            _mPosition.Z = mTranslation.OffsetZ;

            ApplyBoundingBox(null);

            // raise event
            if (YTranslationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                YTranslationCompleted(this, Args);
            }
        }

        /// <summary>
        /// Start Z translation animation
        /// </summary>
        /// <param name="Offset">Length of displacement</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartZTranslation(double Offset, int DurationMs)
        {
            // Add the Cell is the 3D scene if it will come into the bounding box
            Point3D NextPosition = new Point3D(_mPosition.X, _mPosition.Y, _mPosition.Z + Offset);
            if ((mBoundingBox != null) && (mBoundingBox.IsPointInBoundingBox(NextPosition)))
            {
                AddTo3DScene(null);
            }

            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                DoubleAnimation TmpAnimation = new DoubleAnimation();

                TmpAnimation.From = _mPosition.Z;
                TmpAnimation.To = _mPosition.Z + Offset;
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += ZTranslation_Completed;

                mTranslation.BeginAnimation(TranslateTransform3D.OffsetZProperty, TmpAnimation);
            }
            else
            {
                mPosition = NextPosition;

                // raise event
                if (ZTranslationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    ZTranslationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Translation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> ZTranslationCompleted;

        /// <summary>
        /// Translation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void ZTranslation_Completed(object sender, EventArgs e)
        {
            _mPosition.X = mTranslation.OffsetX;
            _mPosition.Y = mTranslation.OffsetY;
            _mPosition.Z = mTranslation.OffsetZ;

            ApplyBoundingBox(null);

            // raise event
            if (ZTranslationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                ZTranslationCompleted(this, Args);
            }
        }


        //-------------------------------------------
        // ROTATION
        //-------------------------------------------

        /// <summary>
        /// Start rotation animation
        /// </summary>
        /// <param name="Angle">The angle to rotate</param>
        /// <param name="DurationMs">The duration in ms</param>
        public void StartXRotation(double Angle, int DurationMs)
        {
            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                Rotation3DAnimation TmpAnimation = new Rotation3DAnimation();

                TmpAnimation.From = new AxisAngleRotation3D(new Vector3D(1, 0, 0), _mAngles.X);
                TmpAnimation.To = new AxisAngleRotation3D(new Vector3D(1, 0, 0), _mAngles.X + Angle);
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += XRotation_Completed;

                mXRotation.BeginAnimation(RotateTransform3D.RotationProperty, TmpAnimation);
            }
            else
            {
                mAngles = new Point3D(_mAngles.X + Angle, _mAngles.Y, _mAngles.Z);

                // raise event
                if (XRotationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    XRotationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Rotation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> XRotationCompleted;

        /// <summary>
        /// Rotation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void XRotation_Completed(object sender, EventArgs e)
        {
            // WPF uses quaternion
            double TmpAngle = (mXRotation.Rotation as QuaternionRotation3D).Quaternion.Angle;
            double TmpAxisDir = (mXRotation.Rotation as QuaternionRotation3D).Quaternion.Axis.X;
            _mAngles.X = TmpAngle * TmpAxisDir;

            ApplyBoundingBox(null);

            // raise event
            if (XRotationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                XRotationCompleted(this, Args);
            }

        }

        /// <summary>
        /// Start rotation animation
        /// </summary>
        /// <param name="Angle">The angle to rotate</param>
        /// <param name="DurationMs">The duration in ms</param>
        public void StartYRotation(double Angle, int DurationMs)
        {
            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                Rotation3DAnimation TmpAnimation = new Rotation3DAnimation();

                TmpAnimation.From = new AxisAngleRotation3D(new Vector3D(0, 1, 0), _mAngles.Y);
                TmpAnimation.To = new AxisAngleRotation3D(new Vector3D(0, 1, 0), _mAngles.Y + Angle);
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += YRotation_Completed;

                mYRotation.BeginAnimation(RotateTransform3D.RotationProperty, TmpAnimation);
            }
            else
            {
                mAngles = new Point3D(_mAngles.X, _mAngles.Y + Angle, _mAngles.Z);

                // raise event
                if (YRotationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    YRotationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Rotation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> YRotationCompleted;

        /// <summary>
        /// Rotation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void YRotation_Completed(object sender, EventArgs e)
        {
            // WPF uses quaternion
            double TmpAngle = (mYRotation.Rotation as QuaternionRotation3D).Quaternion.Angle;
            double TmpAxisDir = (mYRotation.Rotation as QuaternionRotation3D).Quaternion.Axis.Y;
            _mAngles.Y = TmpAngle * TmpAxisDir;

            ApplyBoundingBox(null);

            // raise event
            if (YRotationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                XRotationCompleted(this, Args);
            }

        }

        /// <summary>
        /// Start rotation animation
        /// </summary>
        /// <param name="Angle">The angle to rotate</param>
        /// <param name="DurationMs">The duration in ms</param>
        public void StartZRotation(double Angle, int DurationMs)
        {
            if ((IsAddedTo3DScene) && (DurationMs != 0))
            {
                Rotation3DAnimation TmpAnimation = new Rotation3DAnimation();

                TmpAnimation.From = new AxisAngleRotation3D(new Vector3D(0, 0, 1), _mAngles.Z);
                TmpAnimation.To = new AxisAngleRotation3D(new Vector3D(0, 0, 1), _mAngles.Z + Angle);
                TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
                TmpAnimation.Completed += ZRotation_Completed;

                mZRotation.BeginAnimation(RotateTransform3D.RotationProperty, TmpAnimation);
            }
            else
            {
                mAngles = new Point3D(_mAngles.X, _mAngles.Y, _mAngles.Z + Angle);

                // raise event
                if (ZRotationCompleted != null)
                {
                    UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                    ZRotationCompleted(this, Args);
                }
            }
        }

        /// <summary>
        /// Rotation complete event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> ZRotationCompleted;

        /// <summary>
        /// Rotation completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void ZRotation_Completed(object sender, EventArgs e)
        {
            // WPF uses quaternion
            double TmpAngle = (mZRotation.Rotation as QuaternionRotation3D).Quaternion.Angle;
            double TmpAxisDir = (mZRotation.Rotation as QuaternionRotation3D).Quaternion.Axis.Z;
            _mAngles.Z = TmpAngle * TmpAxisDir;

            ApplyBoundingBox(null);

            // raise event
            if (ZRotationCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                ZRotationCompleted(this, Args);
            }

        }


        //-------------------------------------------
        // SCALING
        //-------------------------------------------

        /// <summary>
        /// Start X scaling animation
        /// </summary>
        /// <param name="Offset">Scale</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartXScaling(double ScaleOffset, int DurationMs)
        {
            DoubleAnimation TmpAnimation = new DoubleAnimation();

            TmpAnimation.From = _mScale.X;
            if (_mScale.X + ScaleOffset >= 0)
            {
                TmpAnimation.To = _mScale.X + ScaleOffset;
            }
            else
            {
                TmpAnimation.To = 0;
            }
            TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
            TmpAnimation.Completed += XScaling_Completed;

            mScaling.BeginAnimation(ScaleTransform3D.ScaleXProperty, TmpAnimation);
        }

        /// <summary>
        /// Scaling completed event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> XScalingCompleted;

        /// <summary>
        /// Scaling completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void XScaling_Completed(object sender, EventArgs e)
        {
            _mScale.X = mScaling.ScaleX;
            _mScale.Y = mScaling.ScaleY;
            _mScale.Z = mScaling.ScaleZ;

            // raise event
            if (XScalingCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                XScalingCompleted(this, Args);
            }
        }

        /// <summary>
        /// Start Y scaling animation
        /// </summary>
        /// <param name="Offset">Scale</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartYScaling(double ScaleOffset, int DurationMs)
        {
            DoubleAnimation TmpAnimation = new DoubleAnimation();

            TmpAnimation.From = _mScale.Y;
            if (_mScale.Y + ScaleOffset >= 0)
            {
                TmpAnimation.To = _mScale.Y + ScaleOffset;
            }
            else
            {
                TmpAnimation.To = 0;
            }
            TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
            TmpAnimation.Completed += YScaling_Completed;

            mScaling.BeginAnimation(ScaleTransform3D.ScaleYProperty, TmpAnimation);
        }

        /// <summary>
        /// Scaling completed event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> YScalingCompleted;

        /// <summary>
        /// Scaling completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void YScaling_Completed(object sender, EventArgs e)
        {
            _mScale.X = mScaling.ScaleX;
            _mScale.Y = mScaling.ScaleY;
            _mScale.Z = mScaling.ScaleZ;

            // raise event
            if (YScalingCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                YScalingCompleted(this, Args);
            }
        }

        /// <summary>
        /// Start Z scaling animation
        /// </summary>
        /// <param name="Offset">Scale</param>
        /// <param name="DurationMs">Duration in millisecond</param>
        public virtual void StartZScaling(double ScaleOffset, int DurationMs)
        {
            DoubleAnimation TmpAnimation = new DoubleAnimation();

            TmpAnimation.From = _mScale.Z;
            if (_mScale.Z + ScaleOffset >= 0)
            {
                TmpAnimation.To = _mScale.Z + ScaleOffset;
            }
            else
            {
                TmpAnimation.To = 0;
            }
            TmpAnimation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, DurationMs));
            TmpAnimation.Completed += ZScaling_Completed;

            mScaling.BeginAnimation(ScaleTransform3D.ScaleZProperty, TmpAnimation);
        }

        /// <summary>
        /// Scaling completed event on which external class can register a handler
        /// </summary>
        public virtual event EventHandler<UserControl3DStatusReport> ZScalingCompleted;

        /// <summary>
        /// Scaling completed event handler
        /// </summary>
        /// <param name="sender">This</param>
        /// <param name="e">EventArgs</param>
        private void ZScaling_Completed(object sender, EventArgs e)
        {
            _mScale.X = mScaling.ScaleX;
            _mScale.Y = mScaling.ScaleY;
            _mScale.Z = mScaling.ScaleZ;

            // raise event
            if (ZScalingCompleted != null)
            {
                UserControl3DStatusReport Args = new UserControl3DStatusReport(this);
                ZScalingCompleted(this, Args);
            }
        }

    }
}
